package com.eternal.data.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;

import androidx.annotation.Nullable;

import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.eternal.base.global.BluetoothKey;
import com.eternal.framework.utils.ConvertUtils;
import com.eternal.framework.utils.KLog;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import io.reactivex.Completable;
import io.reactivex.CompletableEmitter;
import io.reactivex.CompletableOnSubscribe;
import io.reactivex.schedulers.Schedulers;

public abstract class GraphView extends SurfaceView implements SurfaceHolder.Callback, GraphAdapter.Callback {
    private float graphHeight;
    private boolean draggingScroller, scrollerOpen, isPanning;
    private String scrollerLabelText;
    private int graphWidth;
    private long scrollerTimestamp;
    private float standardPadding, scrollerX;
    private Paint graphTimeLinePaint, graphLabelsLightTextPaint, graphLabelsTextPaint,
            graphBoundsLinePaint, labelLinePaint, scrollerLabelTextPaint, graphAvgLinePaint, graphValuePaint,
            scrollerLinePaint, scrollerLabelPaint;
    private GestureDetector gestureDetector;
    private Path graphPath;
    protected GraphAdapter adapter;
    private ScaleGestureDetector scaleGestureDetector;
    private RectF scrollerLabelBounds;
    private Rect scrollerLabelTextBounds, textBounds;
    private SimpleDateFormat monthFormat, yearFormat;
    private SurfaceHolder mHolder;
    private SimpleDateFormat dateFormat;
    private ReentrantReadWriteLock lock;
    private byte port;

    public GraphView(Context context) {
        this(context, null);
    }

    public GraphView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GraphView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void setPort(byte port) {
        this.port = port;
    }

    public void setAdapter(final GraphAdapter adapter) {
        this.adapter = adapter;
        adapter.addCallback(this);
        graphPath = new Path();
    }

    private void init() {
        getHolder().addCallback(this);
        standardPadding = ConvertUtils.sp2px(4.0f);
        textBounds = new Rect();
        scrollerLabelTextBounds = new Rect();
        scrollerLabelBounds = new RectF();
        dateFormat = new SimpleDateFormat("h:mm a, M/d/yy", Locale.ENGLISH);
        monthFormat = new SimpleDateFormat("MMM dd", Locale.ENGLISH);
        yearFormat = new SimpleDateFormat("MMM yyyy", Locale.ENGLISH);

        lock = new ReentrantReadWriteLock();

//        String pattern = ((SimpleDateFormat) SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM)).toPattern();
//        String regex;
//        if (pattern.contains("de")) {
//            regex = "[^Mn]*[Yy]+[^Mn]*";
//        } else {
//            regex = "[^DdMn]*[Yy]+[^DdMn]*";
//        }
//        monthFormat = new SimpleDateFormat(pattern.replaceAll(regex, ""), Locale.ENGLISH);

        initPaint();
        initGestureRecognizers();
    }

    private void initPaint() {
//        Typeface avenirLight = Typeface.createFromAsset(Utils.getContext().getAssets(), "fonts/AvenirLTStd-Light.ttf");

        graphTimeLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphTimeLinePaint.setStyle(Paint.Style.STROKE);
        graphTimeLinePaint.setColor(0xffffffff);
        graphTimeLinePaint.setAlpha(81);
        graphTimeLinePaint.setStrokeWidth(ConvertUtils.sp2px(0.5f));

        graphLabelsLightTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphLabelsLightTextPaint.setStyle(Paint.Style.FILL);
        graphLabelsLightTextPaint.setColor(0xffffffff);
        graphLabelsLightTextPaint.setTextSize(ConvertUtils.sp2px(10f));

        graphLabelsTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphLabelsTextPaint.setStyle(Paint.Style.FILL);
        graphLabelsTextPaint.setColor(0xffffffff);
        graphLabelsTextPaint.setTextSize(ConvertUtils.sp2px(12f));

        graphBoundsLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphBoundsLinePaint.setStyle(Paint.Style.STROKE);
        graphBoundsLinePaint.setColor(0xffffffff);
        graphBoundsLinePaint.setAlpha(81);
        graphBoundsLinePaint.setStrokeWidth(ConvertUtils.sp2px(1f));

        labelLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        labelLinePaint.setStyle(Paint.Style.STROKE);
        labelLinePaint.setColor(0x44ffffff);
        labelLinePaint.setStrokeWidth(ConvertUtils.sp2px(1f));

        scrollerLabelTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrollerLabelTextPaint.setStyle(Paint.Style.FILL);
        scrollerLabelTextPaint.setColor(0xff15BAFF);
        scrollerLabelTextPaint.setTextSize(ConvertUtils.sp2px(12f));

        graphAvgLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphAvgLinePaint.setStyle(Paint.Style.STROKE);
        graphAvgLinePaint.setColor(0xffffffff);
        graphAvgLinePaint.setPathEffect(new DashPathEffect(new float[]{5f, 5f}, 0f));
        graphAvgLinePaint.setAlpha(81);
        graphAvgLinePaint.setStrokeWidth(ConvertUtils.sp2px(1f));

        graphValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        graphValuePaint.setStyle(Paint.Style.STROKE);
        CornerPathEffect effect = new CornerPathEffect(50);
        graphValuePaint.setPathEffect(effect);
        graphValuePaint.setColor(0xff15BAFF);
        graphValuePaint.setStrokeWidth(ConvertUtils.sp2px(1f));

        scrollerLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrollerLinePaint.setStyle(Paint.Style.STROKE);
        scrollerLinePaint.setColor(0xffffffff);
        scrollerLinePaint.setStrokeWidth(ConvertUtils.sp2px(1f));

        scrollerLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrollerLabelPaint.setStyle(Paint.Style.FILL);
        scrollerLabelPaint.setColor(0xffffffff);
    }

    abstract TimeValue getItem(int position);

    abstract int getAvg();

    abstract int getMax();

    abstract int getMin();

    abstract String formatValue(int value);

    private void initGestureRecognizers() {
        gestureDetector = new GestureDetector(getContext(), new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                KLog.i("onSingleTapUp!");
                if (adapter.length >= 2) {
                    scrollerOpen = !scrollerOpen;
                    draggingScroller = false;
                    positionScroller(e.getX());
                }
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                if ((Math.abs(distanceX) > 50 || Math.abs(distanceX) > Math.abs(distanceY) && !isPanning)) {
                    onPanStart(e2);
                    isPanning = true;
                }
                if (isPanning) {
                    onPanMoved(e2, distanceX);
                }
                return isPanning;
            }

            @Override
            public void onLongPress(MotionEvent e) {
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                return false;
            }
        });

        scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                float scale = detector.getScaleFactor();
                if (scale < 0.5f) {
                    scale = 0.5f;
                }
                detector.getFocusX();
                scale = adapter.timeDistance * (1 - scale);
                int dis = (int) (adapter.timeDistance + scale);
                if (dis >= 1800 && dis <= 31536000) {
                    int distance = (int) (detector.getFocusX() / graphWidth * scale);
                    if (distance != 0) {
                        adapter.setVisibleTimeSpace((adapter.minTime - distance), adapter.timeDistance + distance * 2);
                    }
                }
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                KLog.i("Scale end!");
            }
        });
    }

    public void hidePositionScroller(boolean _scrollerOpen) {
        scrollerOpen = _scrollerOpen;
        positionScroller(0);
    }
    private void positionScroller(float x) {
        if (scrollerOpen) {
            int position = adapter.nearestSampleIndexForX(graphWidth, x);
            if(position<0){
                return;
            }else if(adapter.length==2&&(adapter.getItem(0).time>=adapter.minTime+adapter.timeDistance||
                    adapter.getItem(1).time<=adapter.minTime)){
                TimeValue value = getItem(1);
                String model = null;
                if (port == 0 ) {
                    model = adapter.getItem(position).off ? "OFF" : "ON";
                } else if (port > 0){
                    if (adapter.getItem(position).portStateList != null) {
                        if (adapter.getItem(position).portStateList.size() > port-1) {
                            model = adapter.getItem(position).portStateList.get(port-1) ? "OFF" : "ON";;
                        }
                    }
                }
                scrollerTimestamp = value.time;
                Date time = new Date(scrollerTimestamp * 1000L);
                String timeStr;
                timeStr = dateFormat.format(time);
                scrollerLabelText = String.format(Locale.ENGLISH, "%s @ %s", formatValue(value.value), timeStr) + (TextUtils.isEmpty(model) ?"":", "+model);
                scrollerPosition();
            }else{
                TimeValue value = getItem(position);
                float tx=adapter.xFromTime(graphWidth, value.time);
                if(tx < standardPadding || tx > graphWidth - standardPadding){
                    return;
                }
                String model = null;
                if (port == 0 ) {
                    model = adapter.getItem(position).off ? "OFF" : "ON";
                } else if (port > 0){
                    if (adapter.getItem(position).portStateList != null) {
                        if (adapter.getItem(position).portStateList.size() > port-1) {
                            model = adapter.getItem(position).portStateList.get(port-1) ? "OFF" : "ON";;
                        }
                    }
                }
                scrollerTimestamp = value.time;
                Date time = new Date(scrollerTimestamp * 1000L);
                String timeStr;
                timeStr = dateFormat.format(time);
                scrollerLabelText = String.format(Locale.ENGLISH, "%s @ %s", formatValue(value.value), timeStr) + (TextUtils.isEmpty(model) ?"":", "+model);
                scaleScroller();
            }
        }
        postDraw();
    }

    private void scrollerPosition(){
        lock.writeLock().lock();
        int scaledPadding = (int) (standardPadding * 1.5D);
        scrollerX = standardPadding;
        scrollerLabelTextPaint.getTextBounds(scrollerLabelText, 0, scrollerLabelText.length(), scrollerLabelTextBounds);
        int temp = scrollerLabelTextBounds.width();
        scrollerLabelTextPaint.getTextBounds("112", 0, 1, scrollerLabelTextBounds);
        scrollerLabelTextBounds.left = 0;
        scrollerLabelTextBounds.right = temp;
        scrollerLabelTextBounds.offsetTo(((int) (scrollerX - scrollerLabelTextBounds.width() / 2f)),
                (int) ((graphLabelsTextPaint.getTextSize() + standardPadding) * -2f));
        if (scrollerLabelTextBounds.left < scaledPadding) {
            scrollerLabelTextBounds.offsetTo(scaledPadding, scrollerLabelTextBounds.top);
        }
        if (scrollerLabelTextBounds.right + scaledPadding > graphWidth) {
            scrollerLabelTextBounds.offsetTo(graphWidth - scrollerLabelTextBounds.width() - scaledPadding,
                    scrollerLabelTextBounds.top);
        }
        scrollerLabelBounds.set(
                scrollerLabelTextBounds.left - scaledPadding,
                scrollerLabelTextBounds.top - scaledPadding,
                scrollerLabelTextBounds.right + scaledPadding,
                scrollerLabelTextBounds.bottom + scaledPadding);
        lock.writeLock().unlock();
    }

    private void scaleScroller() {
        lock.writeLock().lock();
        int scaledPadding = (int) (standardPadding * 1.5D);
        scrollerX = adapter.xFromTime(graphWidth, scrollerTimestamp);
        if (scrollerX < standardPadding || scrollerX > graphWidth - standardPadding || scrollerLabelText == null) {
            scrollerOpen = false;
            lock.writeLock().unlock();
            return;
        }
        scrollerLabelTextPaint.getTextBounds(scrollerLabelText, 0, scrollerLabelText.length(), scrollerLabelTextBounds);
        int temp = scrollerLabelTextBounds.width();
        scrollerLabelTextPaint.getTextBounds("112", 0, 1, scrollerLabelTextBounds);
        scrollerLabelTextBounds.left = 0;
        scrollerLabelTextBounds.right = temp;
        scrollerLabelTextBounds.offsetTo(((int) (scrollerX - scrollerLabelTextBounds.width() / 2f)),
                (int) ((graphLabelsTextPaint.getTextSize() + standardPadding) * -2f));
        if (scrollerLabelTextBounds.left < scaledPadding) {
            scrollerLabelTextBounds.offsetTo(scaledPadding, scrollerLabelTextBounds.top);
        }
        if (scrollerLabelTextBounds.right + scaledPadding > graphWidth) {
            scrollerLabelTextBounds.offsetTo(graphWidth - scrollerLabelTextBounds.width() - scaledPadding,
                    scrollerLabelTextBounds.top);
        }
        scrollerLabelBounds.set(
                scrollerLabelTextBounds.left - scaledPadding,
                scrollerLabelTextBounds.top - scaledPadding,
                scrollerLabelTextBounds.right + scaledPadding,
                scrollerLabelTextBounds.bottom + scaledPadding);
        lock.writeLock().unlock();
    }

    private void onPanEnd(MotionEvent event) {
        draggingScroller = false;
    }

    private void onPanMoved(MotionEvent event, float dx) {
        if (draggingScroller) {
            positionScroller(event.getX());
        } else {
            long min = adapter.minTime;
            long max = adapter.minTime + adapter.timeDistance;
            int distance;
            if (max > min && graphWidth != 0) {
                distance = (int) (dx / graphWidth * (max - min));
                adapter.setVisibleTimeSpace(min + distance, adapter.timeDistance);
            }
        }
    }

    private void onPanStart(MotionEvent event) {
        if (scrollerOpen) {
            float f = ConvertUtils.sp2px(40);
            if (Math.abs(event.getX() - scrollerX) < f) {
                draggingScroller = true;
            } else {
                RectF temp = new RectF();
                float graphOffsetTop = graphLabelsTextPaint.getTextSize() * 2.0f + standardPadding * 3.0f + standardPadding;
                temp.set(scrollerLabelBounds.left,scrollerLabelBounds.top + graphOffsetTop ,scrollerLabelBounds.right,scrollerLabelBounds.bottom +graphOffsetTop);
                if (temp.contains(event.getX(),event.getY())) {
                    draggingScroller = true;
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (isPanning) {
                onPanEnd(event);
            }
            isPanning = false;
        }
//        scaleGestureDetector.onTouchEvent(event);
//        if (scaleGestureDetector.isInProgress()) {
//            getParent().requestDisallowInterceptTouchEvent(true);
//        } else {
//            gestureDetector.onTouchEvent(event);
//            if (isPanning) {
//                getParent().requestDisallowInterceptTouchEvent(true);
//            }
//        }
        gestureDetector.onTouchEvent(event);
        if (isPanning) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return true;
    }

    @Override
    public void draw() {
        if (mHolder == null || graphPath == null) {
            if (mHolder == null) {
                KLog.e("holder is null");
            } else if (graphPath == null) {
                KLog.e("graphPath is null");
            } else if (!adapter.isReady()) {
                KLog.e("adapter not ready");
            }
            return;
        }
        Canvas canvas = mHolder.lockCanvas();
        if (canvas == null) {
            KLog.e("canvas has locked");
            return;
        }
        //画背景
        canvas.drawRGB(0x14, 0x14, 0x14);
        canvas.save();
        //画最大值
        canvas.translate(0, graphLabelsTextPaint.getTextSize() * 2.0f + standardPadding * 3.0f);
        canvas.drawText(String.format(Locale.ENGLISH, "MAX %s", formatValue(getMax())),
                0, 0, graphLabelsTextPaint);
        //画内容顶部线
        canvas.translate(0, standardPadding);
        canvas.drawLine(0, 0, graphWidth, 0, graphBoundsLinePaint);
        //画底部标签和划分线
        drawTimeLabels(canvas);

        canvas.translate(0, graphHeight);
        canvas.drawLine(0, 0, graphWidth, 0, graphBoundsLinePaint);
        canvas.translate(0, -graphHeight);
        //画折线
        canvas.save();
        canvas.clipRect(0, 0, graphWidth, graphHeight);
        if(adapter.length<2&&adapter.isInit()){
            String tip="No data is available for this period.";
            float x= (graphWidth-graphLabelsTextPaint.measureText(tip))/2;
            float y=(graphHeight+graphLabelsTextPaint.getTextSize())/2;
            canvas.drawText(tip, x, y, graphLabelsTextPaint);
        }
        canvas.drawPath(graphPath, graphValuePaint);
        canvas.restore();
        //如果双击了显示详情
        if (scrollerOpen) {
            lock.readLock().lock();
            canvas.drawLine(scrollerX, graphLabelsTextPaint.getTextSize() * -2f -
                    standardPadding * 2f, scrollerX, graphHeight, scrollerLinePaint);
            float temp = scrollerLabelBounds.width() / 2;
            canvas.drawRoundRect(scrollerLabelBounds, temp, temp, scrollerLabelPaint);
            canvas.drawText(scrollerLabelText, scrollerLabelTextBounds.left,
                    scrollerLabelTextBounds.bottom, scrollerLabelTextPaint);
            lock.readLock().unlock();
        }
        //如果有平均值就画平均值
        int avg = getAvg();
        if (avg != BluetoothKey.EMPTY) {
            float y;
            if (adapter.length <= 1) {
                y = 0;
            } else {
                y = getMax() - getMin();
                if (y <= 10) {
                    y = graphHeight / 2;
                } else {
                    y = (getMax() - avg) * graphHeight / y;
                }
            }

            canvas.drawLine(0, y, graphWidth, y, graphAvgLinePaint);
            String temp = String.format(Locale.ENGLISH, "AVG %s", formatValue(avg));
            graphLabelsTextPaint.getTextBounds(temp, 0, temp.length(), textBounds);
            if (y < textBounds.height() * 2) {
                canvas.drawText(temp, 0, y + standardPadding + textBounds.height(),
                        graphLabelsTextPaint);
            } else if (y < graphHeight - standardPadding - textBounds.height()) {
                canvas.drawText(temp, 0, y - standardPadding, graphLabelsTextPaint);
            }
        }
        //画内容底部线
//        canvas.translate(0, graphHeight);
//        canvas.drawLine(0, 0, graphWidth, 0, graphBoundsLinePaint);
        canvas.translate(0, graphHeight - standardPadding);
        //画最小值
        String temp = String.format(Locale.ENGLISH, "MIN %s", formatValue(getMin()));
        canvas.drawText(temp, 0, 0, graphLabelsTextPaint);
        canvas.translate(0, graphLabelsLightTextPaint.getTextSize() + graphLabelsTextPaint.getTextSize() + standardPadding * 2);
        SimpleDateFormat format;
        if (adapter.timeDistance >= 31536000) {
            format = yearFormat;
        } else {
            format = monthFormat;
        }
        //画开始日期
        temp = format.format(new Date(adapter.minTime * 1000L)).toUpperCase();
        graphLabelsTextPaint.getTextBounds(temp, 0, temp.length(), textBounds);
        canvas.drawText(temp, 0, 0, graphLabelsTextPaint);
        float minX = textBounds.width();
        //画结束日期
        temp = format.format(new Date((adapter.minTime + adapter.timeDistance) * 1000L)).toUpperCase();
        graphLabelsTextPaint.getTextBounds(temp, 0, temp.length(), textBounds);
        canvas.drawText(temp, getWidth() - textBounds.width() - 3, 0, graphLabelsTextPaint);
        float maxX = getWidth() - textBounds.width();
        //画底部的时间线
        int cy = 1 - textBounds.height() / 2;
        canvas.drawLine(minX + standardPadding * 2f, cy, maxX - standardPadding, cy, labelLinePaint);
        canvas.restore();

        mHolder.unlockCanvasAndPost(canvas);
    }

    private void drawTimeLabels(Canvas canvas) {
        List<Date> dates = createLabelTime();
        List<String> labels = createLabelString(dates);
        Date date;
        String label;
        int x;
        Rect rect;
        for (int i = 0; i < dates.size(); ++i) {
            date = dates.get(i);
            label = labels.get(i);
            label = label.toUpperCase();
            x = Math.round(adapter.xFromTime(graphWidth, (int) (date.getTime() / 1000)));
            canvas.drawLine(x, 0, x, graphHeight, graphTimeLinePaint);
            rect = new Rect();
            graphLabelsLightTextPaint.getTextBounds(label, 0, label.length(), rect);
            int half = (rect.width() + 2) / 2;
            int lx;
            if (x >= half) {
                if (graphWidth - x < half) {
                    lx = graphWidth - rect.width() - 2;
                } else {
                    lx = x - half;
                }
            } else {
                lx = 0;
            }
            canvas.drawText(label, lx, graphHeight + graphLabelsLightTextPaint.getTextSize()+ConvertUtils.sp2px(2f),
                    graphLabelsLightTextPaint);
        }
    }

    @NotNull
    private List<Date> createLabelTime() {
        int distance = adapter.timeDistance;
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis((distance * 3 / 2 + adapter.minTime) * 1000L);
        List<Date> dates = new ArrayList<>();
        byte hour = 0, minute = 0, day = 0, month = 0;
        if (distance < 6120) {
            calendar.set(Calendar.MINUTE, roundToInterval(calendar.get(Calendar.MINUTE), 15));
            minute = -15;
        } else if (distance < 10800) {
            calendar.set(Calendar.MINUTE, roundToInterval(calendar.get(Calendar.MINUTE), 30));
            minute = -30;
        } else if (distance < 21600) {
            calendar.set(Calendar.MINUTE, 0);
            hour = -1;
        } else if (distance < 43200) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY,
                    roundToInterval(calendar.get(Calendar.HOUR_OF_DAY), 2));
            hour = -2;
        } else if (distance < 86400) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY,
                    roundToInterval(calendar.get(Calendar.HOUR_OF_DAY), 4));
            hour = -4;
        } else if (distance < 172800) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY,
                    roundToInterval(calendar.get(Calendar.HOUR_OF_DAY), 8));
            hour = -8;
        } else if (distance < 613440) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            day = -1;
        } else if (distance < 1209600) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.FRIDAY,
                    roundToInterval(calendar.get(Calendar.FRIDAY), 2));
            day = -2;
        } else if (distance < 3888000) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.DAY_OF_WEEK, 0);
            day = -7;
        } else if (distance < 7776000) {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.DAY_OF_MONTH, 1);
            Calendar temp = (Calendar) calendar.clone();
            long time;
            long min = adapter.minTime;
            long max = min + adapter.timeDistance;
            do {
                if (temp.get(Calendar.DAY_OF_MONTH) == 1) {
                    temp.add(Calendar.MONTH, -1);
                    temp.set(Calendar.DAY_OF_MONTH, 15);
                } else {
                    temp.set(Calendar.DAY_OF_MONTH, 1);
                }
                Date date = temp.getTime();
                time = date.getTime() / 1000;
                if (time <= max && time >= min) {
                    dates.add(date);
                }
            } while (time >= min);
        } else {
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.HOUR, 0);
            calendar.set(Calendar.DAY_OF_MONTH, 1);
            month = -1;
        }
        if (dates.size() == 0) {
            long time;
            Calendar temp = (Calendar) calendar.clone();
            long min = adapter.minTime;
            long max = min + adapter.timeDistance;
            do {
                temp.add(Calendar.MINUTE, minute);
                temp.add(Calendar.HOUR_OF_DAY, hour);
                temp.add(Calendar.FRIDAY, day);
                temp.add(Calendar.MONTH, month);
                time = temp.getTimeInMillis() / 1000;
                if (time <= max && time >= min) {
                    dates.add(temp.getTime());
                }
            } while (time >= min);
        }
        return dates;
    }

    @NotNull
    private List<String> createLabelString(List<Date> dates) {
        SimpleDateFormat format;
        List<String> labels = new ArrayList<>();
        int distance = adapter.timeDistance;
        if (distance < 10800) {
            format = new SimpleDateFormat("h:mm a", Locale.ENGLISH);
//            if (android.text.format.DateFormat.is24HourFormat(getContext())) {
//                format = new SimpleDateFormat("H:mm", Locale.ENGLISH);
//            } else {
//
//            }
        } else if (distance < 172800) {
            format = new SimpleDateFormat("h a", Locale.ENGLISH);
//            if (android.text.format.DateFormat.is24HourFormat(getContext())) {
//                format = new SimpleDateFormat("HH:mm", Locale.ENGLISH);
//            } else {
//
//            }
        } else if (distance < 345600) {
            long nowTimestamp = System.currentTimeMillis() / 1000;
            long min = adapter.minTime;
            long max = min + adapter.timeDistance;
            format = new SimpleDateFormat("EEE", Locale.ENGLISH);
//            if (nowTimestamp - min > 613440) {
//                format = new SimpleDateFormat("MMM dd", Locale.ENGLISH);
//            } else {
//
//            }
            SimpleDateFormat temp = new SimpleDateFormat("h a", Locale.ENGLISH);
            Iterator<Date> iter = dates.iterator();
            Calendar c = Calendar.getInstance();
            Date date;
            while (iter.hasNext()) {
                date = iter.next();
                c.setTime(date);
                String s;
                if (c.get(Calendar.HOUR_OF_DAY) == 0) {
                    s = format.format(date);
                } else {
                    s = temp.format(date);
                }
                long time = date.getTime() / 1000;
                if (time <= max && time >= min) {
                    labels.add(s);
                }
            }
        } else if (distance < 613440) {
            long nowTime = System.currentTimeMillis() / 1000;
//            if (nowTime - adapter.minTime > 613440) {
//                format = new SimpleDateFormat("MMM dd", Locale.ENGLISH);
//            } else {
//
//            }
            format = new SimpleDateFormat("EEE", Locale.ENGLISH);
        } else if (distance < 3888000) {
            format = new SimpleDateFormat("MMM dd", Locale.ENGLISH);
        } else if (distance < 0x76a700) {
            format = new SimpleDateFormat("MMM dd", Locale.ENGLISH);
            SimpleDateFormat temp = new SimpleDateFormat("dd", Locale.ENGLISH);
            Iterator<Date> iter = dates.iterator();
            Date date;
            Calendar c = Calendar.getInstance();
            long min = adapter.minTime;
            long max = min + adapter.timeDistance;
            while (iter.hasNext()) {
                date = iter.next();
                c.setTime(date);
                String s;
                if (c.get(Calendar.DAY_OF_MONTH) == 1) {
                    s = format.format(date);
                } else {
                    s = temp.format(date);
                }
                long time = date.getTime() / 1000;
                if (time <= max && time >= min) {
                    labels.add(s);
                }
            }
        } else {
            format = new SimpleDateFormat("MMM", Locale.ENGLISH);
        }
        if (labels.size() == 0) {
            for (Date date : dates) {
                labels.add(format.format(date));
            }
        }
        return labels;
    }

    @Contract(pure = true)
    public final int roundToInterval(int n1, int n2) {
        return n1 / n2 * n2;
    }

    @Override
    public void updatePath(int position) {
        Path path = new Path();
        if (graphWidth == 0) {
            KLog.e("width not init");
            return;
        }
        if (adapter.length < 2) {
            graphPath = path;
            return;
        }
        int i = 0, distance = adapter.getDistance();
        long k, max = adapter.minTime + adapter.timeDistance;

        TimeValue value = getItem(0);
        float x = adapter.xFromTime(graphWidth, value.time);
        float y = yFromValue(value.value);
        if(value.value>getMax()||value.value<getMin()){
            TimeValue temp = getItem(1);
            k = valueFormTime(value.time, temp.time, value.value, temp.value, max);
            x=graphWidth;
            y=yFromValue(k);
        }
        path.moveTo(x, y);
        k = value.time;
        i++;
        while (i < adapter.length - 1) {
            value = getItem(i);
            if (adapter.getItem(i + 1).time <= max && (k - value.time >= distance)) {
                x = adapter.xFromTime(graphWidth, value.time);
                y = yFromValue(value.value);
                path.lineTo(x, y);
                if (value.time < adapter.minTime) {
                    break;
                }
                k = value.time;
            }
            i++;
        }
        value = getItem(adapter.length - 1);
        x = adapter.xFromTime(graphWidth, value.time);
        y = yFromValue(value.value);
        if(value.value>getMax()||value.value<getMin()){
            TimeValue temp = getItem(adapter.length - 2);
            k = valueFormTime(temp.time, value.time, temp.value, value.value, adapter.minTime);
            x=0;
            y=yFromValue(k);
        }
        path.lineTo(x, y);
        graphPath = path;
        if (scrollerOpen) {
            scaleScroller();
        }
    }

    public final float yFromValue(float y) {
        if (adapter.length <= 1) {
            return 0;
        }
        int distance = getMax() - getMin();
        if (distance <= 1) {
            return graphHeight / 2;
        }
        return (getMax() + distance / 10f - y) * graphHeight * 5 / distance / 6;
    }

    public final int valueFormTime(long lTime, long nTime, int lValue, int nValue, long now) {
        return (int) (((now - lTime) / (float) (nTime - lTime) * (nValue - lValue)) + lValue);
    }

    private void postDraw() {
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                draw();
            }
        }).subscribeOn(Schedulers.computation()).subscribe();
    }

    private void postUpdateDraw(){
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                updatePath(0);
                draw();
            }
        }).subscribeOn(Schedulers.computation()).subscribe();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        KLog.e("surfaceCreated");
        mHolder = holder;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        graphHeight = height - graphLabelsTextPaint.getTextSize() * 3f - graphLabelsLightTextPaint.getTextSize() - standardPadding * 5f;
        graphWidth = width;
        if (adapter != null) {
            postUpdateDraw();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        KLog.e("surfaceDestroyed");
        mHolder = null;
    }

    public void setScrollerOpen(boolean scrollerOpen) {
        this.scrollerOpen = scrollerOpen;
    }
}
